<?php

namespace app\model;

use app\model\tablenot\tool\RedisService;
use app\model\tablenot\tool\Tool;
use think\facade\Db;
use \think\Model;

/**
 * 所有数据库表实例化的model都必须继承这个model
 * Class BaseModel
 * @package app\model
 */
class BaseModel extends Model
{
    // 该表是否缓存到redis
    public $tableToRedis = false;

    // 该表是否允许全表缓存到单个redis
    public $tableListToRedis = false;

    // 该表缓存到redis的策略
    public $tableToRedisKey = [];

    // 不允许通过add方法修改的字段
    public $notAllowAddColumn = [];

    /**
     * @return BaseModel
     */
    public static function instance()
    {
        $class = get_called_class();
        return new $class();
    }

    // 根据where查询一条数据
    public function findOne($where, $isArr = true)
    {
        $select = "";
        if (isset($where['select'])) {
            $select = $where['select'];
            unset($where['select']);
        }
        $query = $this;
        if (!empty($select)) {
            $query = $query->field($select);
        }
        // 自动入缓存,只支持单个model配置的redis key入缓存
        $RedisServiceModel = new RedisService();
        $redisKey = "table:" . $this->table;
        $hashkey = "";

        // where条件只支持['a'=>b,'c'=>d], 不支持[['a','=','b'],['c','=','d']]
        $forceNoCache = false;
        // 如果where条件在缓存策略中，那么就要缓存数据  where 值要全部转换成字符串
        foreach ($where as $key => $val) {
            if(is_array($val)){
                $forceNoCache = true;
                break;
            }else{
                $where[$key] = (string)$val;
            }
        }
        if(!$forceNoCache){
            $whereCoulumn = array_keys($where);
            foreach ($this->tableToRedisKey as $val) {
                $modelRedisCache = explode("|", $val);
                if (Tool::arrIsSame($whereCoulumn, $modelRedisCache)) {
                    // 为了防止多字段缓存A|b和B|A生成两条缓存，这里强制排序，只留下一条
                    ksort($where);
                    $hashkey = json_encode($where, JSON_UNESCAPED_UNICODE);
                }
            }
            // 如果配置了缓存，先考虑从缓存中取数据
            if ($this->tableToRedis == true && !empty($hashkey)) {
                $json = $RedisServiceModel->hGet($redisKey, $hashkey);
                if (!empty($json)) {
                    $jsonObj = json_decode($json, true);
                    if ($isArr) {
                        return $jsonObj;
                    } else {
                        return $this->find($jsonObj['id']);
                    }
                }
            }
        }
        // 缓存没有数据或者不走缓存，查询数据库
        $query = $query->where($where);
        $obj = $query->find();

        // 查询的数据存入redis
        if (!empty($obj) && $this->tableToRedis == true && !empty($hashkey)) {
            $RedisServiceModel->hSet($redisKey, $hashkey, json_encode($obj, JSON_UNESCAPED_UNICODE));
        }

        // 返回数据
        if ($isArr) {
            $obj = empty($obj) ? "" : $obj->toArray();
        }
        return $obj;
    }

    // 根据where查询多条数据
    public function findMore($where, $isArr = true)
    {
        // 查询条件
        $select = "";
        if (isset($where['select'])) {
            $select = $where['select'];
            unset($where['select']);
        }
        // 排序条件
        $orderBy = "";
        if (isset($where['orderBy'])) {
            $orderBy = $where['orderBy'];
            unset($where['orderBy']);
        }
        $group = "";
        if (isset($where['group'])) {
            $group = $where['group'];
            unset($where['group']);
        }

        //缺省最大为3000 不能信任前台输入
        $limit = 3000;
        if (isset($where['limit'])) {
            $limit = $where['limit'];
            unset($where['limit']);
            $limit = min($limit,3000);
        }

        // 生成查询语句
        $query = $this;
        if (!empty($select)) {
            $query = $query->field($select);
        }
        if (!empty($orderBy)) {
            $query = $query->order($orderBy);
        }
        if (!empty($group)) {
            $query = $query->group($group);
        }
        if (!empty($limit)) {
            $query = $query->limit($limit);
        }
        if($isArr === "count"){
            return $query->where($where)->count();
        }else{
            $obj = $query->where($where)->select();
            if ($isArr) {
                return empty($obj) ? "" : $obj->toArray();
            } else {
                return $obj;
            }
        }
    }

    /**
     * 分页获取数据
     * @param int $where
     * @param bool $isArr
     * @return BaseModel|array|string|\think\Paginator
     * @throws \think\db\exception\DbException
     */
    public function page($where, $isArr = true)
    {
        $pagesize = 20;
        if (isset($where['pagesize'])) {
            $pagesize = $where['pagesize'];
            unset($where['pagesize']);
        }

        // 查询条件
        $select = "";
        if (isset($where['select'])) {
            $select = $where['select'];
            unset($where['select']);
        }
        $group = "";
        if (isset($where['group'])) {
            $group = $where['group'];
            unset($where['group']);
        }
        // 查询条件
        $unselect = "";
        if (isset($where['unselect'])) {
            $unselect = $where['unselect'];
            unset($where['unselect']);
        }
        // 排序条件
        $orderBy = "";
        if (isset($where['orderBy'])) {
            $orderBy = $where['orderBy'];
            unset($where['orderBy']);
        }
        // 生成查询语句
        $query = $this;
        if (!empty($select)) {
            $query = $query->field($select);
        }
        if (!empty($unselect)) {
            $query = $query->withoutField($unselect, true);
        }
        if (!empty($orderBy)) {
            $query = $query->order($orderBy);
        }
        if (!empty($group)) {
            $query = $query->group($group);
        }
        $obj = $query->where($where)->paginate($pagesize);
        if ($isArr) {
            return empty($obj) ? "" : $obj->toArray();
        } else {
            return $obj;
        }
    }

    /**
     *  获取表的所有数据
     */
    public function tableList()
    {
        if($this->tableListToRedis){
            $RedisServiceModel = new RedisService();
            $redisKey = "table_list:" . $this->table;
            $redisData = $RedisServiceModel->get($redisKey);
            if(empty($redisData)){
                $obj = $this->select()->toArray();
                if(!empty($obj)){
                    $RedisServiceModel->set($redisKey, json_encode($obj, JSON_UNESCAPED_UNICODE));
                }
            }else{
                $obj = json_decode($redisData, true);
            }
        }else{
            $obj = $this->select()->toArray();
        }
        return $obj;
    }

    /**
     * 添加一条数据
     * @param $data
     * @return $this
     */
    public function addOne($data)
    {
        // 新增数据不能有id
        unset($data['id']);
        $this->save($data);
        // 删除掉全表缓存
        $this->delRedisTableList();
        return $this;
    }

    /**
     * 更新一条数据，不存在则更新，不会创建
     * 后台可以直接根据id进行更新,api大多数情况下需要验证是不是自己的
     * @param $data
     * @param array $extWhere 默认是主键更新 如 $extWhere = [['user_id','eq','$userId']],
     * @return $this|string
     */
    public function updateOne($data,$extWhere=[])
    {
        try {
            if(!isset($data['id'])){
                throw new MyException('缺少id');
            }
            // 根据旧数据删除缓存
            $this->delRedisTableAuto($data['id']);
            // 过滤掉不允许通过add方法修改的字段
            foreach ($data as $key => $val) {
                if (in_array($key, $this->notAllowAddColumn)) {
                    unset($data[$key]);
                }
            }
            $where[] = ['id','=',$data['id']];
            if($extWhere){
                $where = array_merge($where,$extWhere);
            }
            $this->where($where)->update($data);
            return $this->findOne($where, true);
        }catch (MyException $e) {
            echo $e->toJson();
        }
    }

    /**
     * 更新多条数据
     * @param $data
     * @return $this|string
     */
    public function updateMore($data, $where)
    {
        $this->delRedisTable();
        $this->delRedisTableList();
        return $this->update($data, $where);
    }

    /**
     * 删除一条数据
     * @return $this|string
     */
    public function delOne($id)
    {
        // 先删除缓存再删除数据，避免出现数据修改缓存不变的情况
        $this->delRedisTableAuto($id);
        return $this::destroy($id);
    }

    /**
     * 删除多条数据
     * @param $where
     * @return $this|string
     */
    public function delMore($where)
    {
        // 先删除缓存再删除数据，避免出现数据修改缓存不变的情况
        $this->delRedisTable();
        $this->delRedisTableList();
        return $this->where($where)->delete();
    }

    /**
     * 自动删除redis缓存
     * @param $id 需要清除缓存的表的主键id的值
     */
    public function delRedisTableAuto($id)
    {
        $RedisServiceModel = new RedisService();
        // 查询到了旧数据
        $old_obj = $this->findOne(['id' => $id], true);
        // 如果开启了字段缓存，那么就删除所有相关的字段缓存
        if(!empty($old_obj) && $this->tableToRedis){
            foreach ($this->tableToRedisKey as $key) {
                $hashkey = array();
                $keyArr = explode("|", $key);
                foreach ($keyArr as $v) {
                    $hashkey[$v] = (string)$old_obj[$v];
                }
                ksort($hashkey);
                $hashkey = json_encode($hashkey, JSON_UNESCAPED_UNICODE);
                $RedisServiceModel->hDel("table:" . $this->table, $hashkey);
            }
        }
        // 如果开启了表缓存，那么就删除表缓存
        $this->delRedisTableList();
    }

    /**
     * 删除全表缓存
     * @return bool
     */
    public function delRedisTable()
    {
        if($this->tableToRedis){
            $RedisServiceModel = new RedisService();
            $tableListKey = "table:" . $this->table;
            $RedisServiceModel->Del($tableListKey);
        }
        return true;
    }

    /**
     * 删除全表缓存
     * @return bool
     */
    public function delRedisTableList()
    {
        if($this->tableListToRedis){
            $RedisServiceModel = new RedisService();
            $tableListKey = "table_list:" . $this->table;
            $RedisServiceModel->Del($tableListKey);
        }
        return true;
    }

    /**
     * 批量插入数据库数据  注意，次方法不是静态方法
     * 要求批量插入的数据字段保持一直，否则报错，调用前需要自行验证
     * data最少得有一条数据，否则报错，调用前需要自行验证
     * @param $data
     * @return bool
     */
    public function insertMore($data)
    {
        // 由于受数据库sql语句长度限制，这里得限制一次最大sql插入条数
        $limit = 10000;
        $table = $this->table;
        $foreachData = array_chunk($data, $limit);
        foreach ($foreachData as $val) {
            $sql = $this->getInsertSql($table, $val);
            Db::query($sql);
        }
        return true;
    }

    // 批量插入数据的时候获取执行的sql
    private function getInsertSql($table, $data)
    {
        // 获取需要插入的字符安
        $column = array_keys(current($data));
        $columnStr = "`" . implode("`,`", $column) . "`";
        $valuesArr = array();
        foreach ($data as $val) {
            $valuesArr[] = "('" . implode("','", $val) . "')";
        }
        $valuesStr = implode(",", $valuesArr);
        $sql = "insert into {$table} ($columnStr) values {$valuesStr};";
        return $sql;
    }


############################# 自动关联表 方法 开始######################################
    // 处理 不显示的关联表的数据
    public function dealDataClear(&$data, $query)
    {
        // 获取需要清除的字段 1,不需要查询的字段   2关联数据时候 joinBefore回导致查询不需要的字段
        $clearColumn = array();
        if (isset($query['databaseOperate']['noSelect'])) {
            $clearColumn = array_merge($clearColumn, $query['databaseOperate']['noSelect']);
        }

        if (isset($query['databaseOperate']['joinWith'])) {
            $clearColumnArr = array_diff(array_keys($this->joinWithList), $query['databaseOperate']['joinWith']);
            $clearColumn = array_merge($clearColumn, $clearColumnArr);
        }
        foreach ($data as &$val) {
            foreach ($clearColumn as $column) {
                unset($val[$column]);
            }
        }
    }

    // 处理关联表数据
    public function dealJoinWith(&$data, $joinWith)
    {
        if (!empty($data) && isset($joinWith)) {
            $joinWithList = $this->joinWithList;
            // 关联数据
            foreach ($joinWith as $joinModel) {
                // 如果一个对象是依赖于
                if (isset($joinWithList[$joinModel]['joinBefore'])) {
                    $this->joinWithColumn($data, $joinWithList, $joinWithList[$joinModel]['joinBefore']);
                    $this->joinWithBeforeColumn($data, $joinWithList, $joinModel);
                } else {
                    $this->joinWithColumn($data, $joinWithList, $joinModel);
                }
            }
        }
    }

    // 正常的关联表数据
    private function joinWithColumn(&$data, $joinWithList, $joinModel)
    {
        // 有时候 joinBefore执行了，后面可能还会再执行一次，这里检查一下，如果有这个数据了就跳过去
        $endData = end($data);
        if (!isset($endData[$joinModel])) {
            $selfColumnStr = $joinWithList[$joinModel]['selfColumn'];
            $selfColumnArr = explode(",", $selfColumnStr);
            $joinColumnStr = $joinWithList[$joinModel]['joinColumn'];
            $joinColumnArr = explode(",", $joinColumnStr);
            $joinWithColumnIdArr = array_column($data, $selfColumnArr[0]);
            $find = $joinWithList[$joinModel]['joinObj'];
            $modelObjs = $find::field($joinWithList[$joinModel]['selectColumn'])
                ->where($joinColumnArr[0], 'in', $joinWithColumnIdArr)
                ->select()
                ->toArray();
            foreach ($data as &$v) {
                $v[$joinModel] = array();
                foreach ($modelObjs as $val) {
                    $toJoin = true;
                    foreach ($selfColumnArr as $key => $column) {
                        if ($v[$selfColumnArr[$key]] != $val[$joinColumnArr[$key]]) {
                            $toJoin = false;
                        }
                    }
                    if ($toJoin) {
                        $v[$joinModel] = $val;
                    }
                }
            }
        }
    }

    // 需要先关联前置表才能关联现在的表关联数据
    private function joinWithBeforeColumn(&$data, $joinWithList, $joinModel)
    {
        // 有时候 joinBefore执行了，后面可能还会再执行一次，这里检查一下，如果有这个数据了就跳过去
        $endData = end($data);
        if (!isset($endData[$joinModel])) {
            $selfColumnArr = explode(".", $joinWithList[$joinModel]['selfColumn']);
            $joinWithColumnIdArr = array();
            foreach ($data as $v) {
                if (isset($v[$selfColumnArr[0]][$selfColumnArr[1]])) {
                    array_push($joinWithColumnIdArr, $v[$selfColumnArr[0]][$selfColumnArr[1]]);
                }
            }
            $joinWithColumnIdArr = array_unique($joinWithColumnIdArr);

            $find = $joinWithList[$joinModel]['joinObj'];
            $modelObjs = $find::field($joinWithList[$joinModel]['selectColumn'])
                ->where($joinWithList[$joinModel]['joinColumn'], 'in', implode(",",$joinWithColumnIdArr))
                //->indexBy($joinWithList[$joinModel]['joinColumn']) tp6没有这个语法
                ->select()
                ->toArray();
            $modelObjs = Tool::initArrSubscript($modelObjs, $joinWithList[$joinModel]['joinColumn']);
            foreach ($data as &$v) {
                if (isset($v[$selfColumnArr[0]][$selfColumnArr[1]]) && isset($modelObjs[$v[$selfColumnArr[0]][$selfColumnArr[1]]])) {
                    $v[$joinModel] = $modelObjs[$v[$selfColumnArr[0]][$selfColumnArr[1]]];
                } else {
                    $v[$joinModel] = [];
                }
            }
        }
    }
############################# 自动关联表 方法 结束######################################
}
